// Copyright 2017 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "dawn/native/metal/TextureMTL.h"

#include "dawn/common/Constants.h"
#include "dawn/common/Math.h"
#include "dawn/common/Platform.h"
#include "dawn/native/DynamicUploader.h"
#include "dawn/native/EnumMaskIterator.h"
#include "dawn/native/metal/BufferMTL.h"
#include "dawn/native/metal/DeviceMTL.h"
#include "dawn/native/metal/UtilsMetal.h"

#include <CoreVideo/CVPixelBuffer.h>

namespace dawn::native::metal {

namespace {

MTLTextureUsage MetalTextureUsage(const Format& format, wgpu::TextureUsage usage) {
    MTLTextureUsage result = MTLTextureUsageUnknown;  // This is 0

    if (usage & (wgpu::TextureUsage::StorageBinding)) {
        result |= MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead;
    }

    if (usage & (wgpu::TextureUsage::TextureBinding)) {
        result |= MTLTextureUsageShaderRead;

        // For sampling stencil aspect of combined depth/stencil.
        // See TextureView::Initialize.
        // Depth views for depth/stencil textures in Metal simply use the original
        // texture's format, but stencil views require format reinterpretation.
        if (IsSubset(Aspect::Depth | Aspect::Stencil, format.aspects)) {
            result |= MTLTextureUsagePixelFormatView;
        }
    }

    if (usage & wgpu::TextureUsage::RenderAttachment) {
        result |= MTLTextureUsageRenderTarget;
    }

    return result;
}

MTLTextureType MetalTextureViewType(wgpu::TextureViewDimension dimension,
                                    unsigned int sampleCount) {
    switch (dimension) {
        case wgpu::TextureViewDimension::e1D:
            return MTLTextureType1D;
        case wgpu::TextureViewDimension::e2D:
            return (sampleCount > 1) ? MTLTextureType2DMultisample : MTLTextureType2D;
        case wgpu::TextureViewDimension::e2DArray:
            return MTLTextureType2DArray;
        case wgpu::TextureViewDimension::Cube:
            return MTLTextureTypeCube;
        case wgpu::TextureViewDimension::CubeArray:
            return MTLTextureTypeCubeArray;
        case wgpu::TextureViewDimension::e3D:
            return MTLTextureType3D;

        case wgpu::TextureViewDimension::Undefined:
            UNREACHABLE();
    }
}

bool RequiresCreatingNewTextureView(const TextureBase* texture,
                                    const TextureViewDescriptor* textureViewDescriptor) {
    constexpr wgpu::TextureUsage kShaderUsageNeedsView =
        wgpu::TextureUsage::StorageBinding | wgpu::TextureUsage::TextureBinding;
    constexpr wgpu::TextureUsage kUsageNeedsView =
        kShaderUsageNeedsView | wgpu::TextureUsage::RenderAttachment;
    if ((texture->GetInternalUsage() & kUsageNeedsView) == 0) {
        return false;
    }

    if (texture->GetFormat().format != textureViewDescriptor->format &&
        !texture->GetFormat().HasDepthOrStencil()) {
        // Color format reinterpretation required.
        // Note: Depth/stencil formats don't support reinterpretation.
        // See also TextureView::GetAttachmentInfo when modifying this condition.
        return true;
    }

    // Reinterpretation not required. Now, we only need a new view if the view dimension or
    // set of subresources for the shader is different from the base texture.
    if ((texture->GetInternalUsage() & kShaderUsageNeedsView) == 0) {
        return false;
    }

    if (texture->GetArrayLayers() != textureViewDescriptor->arrayLayerCount ||
        (texture->GetArrayLayers() == 1 && texture->GetDimension() == wgpu::TextureDimension::e2D &&
         textureViewDescriptor->dimension == wgpu::TextureViewDimension::e2DArray)) {
        // If the view has a different number of array layers, we need a new view.
        // And, if the original texture is a 2D texture with one array layer, we need a new
        // view to view it as a 2D array texture.
        return true;
    }

    if (texture->GetNumMipLevels() != textureViewDescriptor->mipLevelCount) {
        return true;
    }

    // If the texture is created with MTLTextureUsagePixelFormatView, we need
    // a new view to perform format reinterpretation.
    if ((MetalTextureUsage(texture->GetFormat(), texture->GetInternalUsage()) &
         MTLTextureUsagePixelFormatView) != 0) {
        return true;
    }

    switch (textureViewDescriptor->dimension) {
        case wgpu::TextureViewDimension::Cube:
        case wgpu::TextureViewDimension::CubeArray:
            return true;
        default:
            break;
    }

    return false;
}

// Metal only allows format reinterpretation to happen on swizzle pattern or conversion
// between linear space and sRGB without setting MTLTextureUsagePixelFormatView flag. For
// example, creating bgra8Unorm texture view on rgba8Unorm texture or creating
// rgba8Unorm_srgb texture view on rgab8Unorm texture.
bool AllowFormatReinterpretationWithoutFlag(MTLPixelFormat origin,
                                            MTLPixelFormat reinterpretation) {
#define SRGB_PAIR(a, b)               \
    case a:                           \
        return reinterpretation == b; \
    case b:                           \
        return reinterpretation == a

    switch (origin) {
        case MTLPixelFormatRGBA8Unorm:
            return reinterpretation == MTLPixelFormatBGRA8Unorm ||
                   reinterpretation == MTLPixelFormatRGBA8Unorm_sRGB;
        case MTLPixelFormatBGRA8Unorm:
            return reinterpretation == MTLPixelFormatRGBA8Unorm ||
                   reinterpretation == MTLPixelFormatBGRA8Unorm_sRGB;
        case MTLPixelFormatRGBA8Unorm_sRGB:
            return reinterpretation == MTLPixelFormatBGRA8Unorm_sRGB ||
                   reinterpretation == MTLPixelFormatRGBA8Unorm;
        case MTLPixelFormatBGRA8Unorm_sRGB:
            return reinterpretation == MTLPixelFormatRGBA8Unorm_sRGB ||
                   reinterpretation == MTLPixelFormatBGRA8Unorm;

#if DAWN_PLATFORM_IS(MACOS)
            SRGB_PAIR(MTLPixelFormatBC1_RGBA, MTLPixelFormatBC1_RGBA_sRGB);
            SRGB_PAIR(MTLPixelFormatBC2_RGBA, MTLPixelFormatBC2_RGBA_sRGB);
            SRGB_PAIR(MTLPixelFormatBC3_RGBA, MTLPixelFormatBC3_RGBA_sRGB);
            SRGB_PAIR(MTLPixelFormatBC7_RGBAUnorm, MTLPixelFormatBC7_RGBAUnorm_sRGB);
#endif
        default:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                switch (origin) {
                    SRGB_PAIR(MTLPixelFormatEAC_RGBA8, MTLPixelFormatEAC_RGBA8_sRGB);

                    SRGB_PAIR(MTLPixelFormatETC2_RGB8, MTLPixelFormatETC2_RGB8_sRGB);
                    SRGB_PAIR(MTLPixelFormatETC2_RGB8A1, MTLPixelFormatETC2_RGB8A1_sRGB);

                    SRGB_PAIR(MTLPixelFormatASTC_4x4_LDR, MTLPixelFormatASTC_4x4_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_5x4_LDR, MTLPixelFormatASTC_5x4_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_5x5_LDR, MTLPixelFormatASTC_5x5_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_6x5_LDR, MTLPixelFormatASTC_6x5_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_6x6_LDR, MTLPixelFormatASTC_6x6_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_8x5_LDR, MTLPixelFormatASTC_8x5_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_8x6_LDR, MTLPixelFormatASTC_8x6_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_8x8_LDR, MTLPixelFormatASTC_8x8_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_10x5_LDR, MTLPixelFormatASTC_10x5_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_10x6_LDR, MTLPixelFormatASTC_10x6_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_10x8_LDR, MTLPixelFormatASTC_10x8_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_10x10_LDR, MTLPixelFormatASTC_10x10_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_12x10_LDR, MTLPixelFormatASTC_12x10_sRGB);
                    SRGB_PAIR(MTLPixelFormatASTC_12x12_LDR, MTLPixelFormatASTC_12x12_sRGB);

                    default:
                        break;
                }
            }

            return false;
    }
#undef SRGB_PAIR
}

ResultOrError<wgpu::TextureFormat> GetFormatEquivalentToIOSurfaceFormat(uint32_t format) {
    switch (format) {
        case kCVPixelFormatType_64RGBAHalf:
            return wgpu::TextureFormat::RGBA16Float;
        case kCVPixelFormatType_TwoComponent16Half:
            return wgpu::TextureFormat::RG16Float;
        case kCVPixelFormatType_OneComponent16Half:
            return wgpu::TextureFormat::R16Float;
        case kCVPixelFormatType_ARGB2101010LEPacked:
            return wgpu::TextureFormat::RGB10A2Unorm;
        case kCVPixelFormatType_32RGBA:
            return wgpu::TextureFormat::RGBA8Unorm;
        case kCVPixelFormatType_32BGRA:
            return wgpu::TextureFormat::BGRA8Unorm;
        case kCVPixelFormatType_TwoComponent8:
            return wgpu::TextureFormat::RG8Unorm;
        case kCVPixelFormatType_OneComponent8:
            return wgpu::TextureFormat::R8Unorm;
        case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
            return wgpu::TextureFormat::R8BG8Biplanar420Unorm;
        default:
            return DAWN_VALIDATION_ERROR("Unsupported IOSurface format (%x).", format);
    }
}

uint32_t GetIOSurfacePlane(wgpu::TextureAspect aspect) {
    switch (aspect) {
        case wgpu::TextureAspect::Plane0Only:
            return 0;
        case wgpu::TextureAspect::Plane1Only:
            return 1;
        default:
            UNREACHABLE();
    }
}

#if DAWN_PLATFORM_IS(MACOS)
MTLStorageMode kIOSurfaceStorageMode = MTLStorageModeManaged;
#elif DAWN_PLATFORM_IS(IOS)
MTLStorageMode kIOSurfaceStorageMode = MTLStorageModePrivate;
#else
#error "Unsupported Apple platform."
#endif
}  // namespace

MTLPixelFormat MetalPixelFormat(const DeviceBase* device, wgpu::TextureFormat format) {
    switch (format) {
        case wgpu::TextureFormat::R8Unorm:
            return MTLPixelFormatR8Unorm;
        case wgpu::TextureFormat::R8Snorm:
            return MTLPixelFormatR8Snorm;
        case wgpu::TextureFormat::R8Uint:
            return MTLPixelFormatR8Uint;
        case wgpu::TextureFormat::R8Sint:
            return MTLPixelFormatR8Sint;

        case wgpu::TextureFormat::R16Uint:
            return MTLPixelFormatR16Uint;
        case wgpu::TextureFormat::R16Sint:
            return MTLPixelFormatR16Sint;
        case wgpu::TextureFormat::R16Float:
            return MTLPixelFormatR16Float;
        case wgpu::TextureFormat::RG8Unorm:
            return MTLPixelFormatRG8Unorm;
        case wgpu::TextureFormat::RG8Snorm:
            return MTLPixelFormatRG8Snorm;
        case wgpu::TextureFormat::RG8Uint:
            return MTLPixelFormatRG8Uint;
        case wgpu::TextureFormat::RG8Sint:
            return MTLPixelFormatRG8Sint;

        case wgpu::TextureFormat::R32Uint:
            return MTLPixelFormatR32Uint;
        case wgpu::TextureFormat::R32Sint:
            return MTLPixelFormatR32Sint;
        case wgpu::TextureFormat::R32Float:
            return MTLPixelFormatR32Float;
        case wgpu::TextureFormat::RG16Uint:
            return MTLPixelFormatRG16Uint;
        case wgpu::TextureFormat::RG16Sint:
            return MTLPixelFormatRG16Sint;
        case wgpu::TextureFormat::RG16Float:
            return MTLPixelFormatRG16Float;
        case wgpu::TextureFormat::RGBA8Unorm:
            return MTLPixelFormatRGBA8Unorm;
        case wgpu::TextureFormat::RGBA8UnormSrgb:
            return MTLPixelFormatRGBA8Unorm_sRGB;
        case wgpu::TextureFormat::RGBA8Snorm:
            return MTLPixelFormatRGBA8Snorm;
        case wgpu::TextureFormat::RGBA8Uint:
            return MTLPixelFormatRGBA8Uint;
        case wgpu::TextureFormat::RGBA8Sint:
            return MTLPixelFormatRGBA8Sint;
        case wgpu::TextureFormat::BGRA8Unorm:
            return MTLPixelFormatBGRA8Unorm;
        case wgpu::TextureFormat::BGRA8UnormSrgb:
            return MTLPixelFormatBGRA8Unorm_sRGB;
        case wgpu::TextureFormat::RGB10A2Unorm:
            return MTLPixelFormatRGB10A2Unorm;
        case wgpu::TextureFormat::RG11B10Ufloat:
            return MTLPixelFormatRG11B10Float;
        case wgpu::TextureFormat::RGB9E5Ufloat:
            return MTLPixelFormatRGB9E5Float;

        case wgpu::TextureFormat::RG32Uint:
            return MTLPixelFormatRG32Uint;
        case wgpu::TextureFormat::RG32Sint:
            return MTLPixelFormatRG32Sint;
        case wgpu::TextureFormat::RG32Float:
            return MTLPixelFormatRG32Float;
        case wgpu::TextureFormat::RGBA16Uint:
            return MTLPixelFormatRGBA16Uint;
        case wgpu::TextureFormat::RGBA16Sint:
            return MTLPixelFormatRGBA16Sint;
        case wgpu::TextureFormat::RGBA16Float:
            return MTLPixelFormatRGBA16Float;

        case wgpu::TextureFormat::RGBA32Uint:
            return MTLPixelFormatRGBA32Uint;
        case wgpu::TextureFormat::RGBA32Sint:
            return MTLPixelFormatRGBA32Sint;
        case wgpu::TextureFormat::RGBA32Float:
            return MTLPixelFormatRGBA32Float;

        case wgpu::TextureFormat::Depth32Float:
            return MTLPixelFormatDepth32Float;
        case wgpu::TextureFormat::Depth24Plus:
            return MTLPixelFormatDepth32Float;
        case wgpu::TextureFormat::Depth24PlusStencil8:
        case wgpu::TextureFormat::Depth32FloatStencil8:
            return MTLPixelFormatDepth32Float_Stencil8;
        case wgpu::TextureFormat::Depth16Unorm:
            if (@available(macOS 10.12, iOS 13.0, *)) {
                return MTLPixelFormatDepth16Unorm;
            }
            UNREACHABLE();
        case wgpu::TextureFormat::Stencil8:
            if (device->IsToggleEnabled(Toggle::MetalUseCombinedDepthStencilFormatForStencil8)) {
                return MTLPixelFormatDepth32Float_Stencil8;
            }
            return MTLPixelFormatStencil8;

#if DAWN_PLATFORM_IS(MACOS)
        case wgpu::TextureFormat::BC1RGBAUnorm:
            return MTLPixelFormatBC1_RGBA;
        case wgpu::TextureFormat::BC1RGBAUnormSrgb:
            return MTLPixelFormatBC1_RGBA_sRGB;
        case wgpu::TextureFormat::BC2RGBAUnorm:
            return MTLPixelFormatBC2_RGBA;
        case wgpu::TextureFormat::BC2RGBAUnormSrgb:
            return MTLPixelFormatBC2_RGBA_sRGB;
        case wgpu::TextureFormat::BC3RGBAUnorm:
            return MTLPixelFormatBC3_RGBA;
        case wgpu::TextureFormat::BC3RGBAUnormSrgb:
            return MTLPixelFormatBC3_RGBA_sRGB;
        case wgpu::TextureFormat::BC4RSnorm:
            return MTLPixelFormatBC4_RSnorm;
        case wgpu::TextureFormat::BC4RUnorm:
            return MTLPixelFormatBC4_RUnorm;
        case wgpu::TextureFormat::BC5RGSnorm:
            return MTLPixelFormatBC5_RGSnorm;
        case wgpu::TextureFormat::BC5RGUnorm:
            return MTLPixelFormatBC5_RGUnorm;
        case wgpu::TextureFormat::BC6HRGBFloat:
            return MTLPixelFormatBC6H_RGBFloat;
        case wgpu::TextureFormat::BC6HRGBUfloat:
            return MTLPixelFormatBC6H_RGBUfloat;
        case wgpu::TextureFormat::BC7RGBAUnorm:
            return MTLPixelFormatBC7_RGBAUnorm;
        case wgpu::TextureFormat::BC7RGBAUnormSrgb:
            return MTLPixelFormatBC7_RGBAUnorm_sRGB;
#else
        case wgpu::TextureFormat::BC1RGBAUnorm:
        case wgpu::TextureFormat::BC1RGBAUnormSrgb:
        case wgpu::TextureFormat::BC2RGBAUnorm:
        case wgpu::TextureFormat::BC2RGBAUnormSrgb:
        case wgpu::TextureFormat::BC3RGBAUnorm:
        case wgpu::TextureFormat::BC3RGBAUnormSrgb:
        case wgpu::TextureFormat::BC4RSnorm:
        case wgpu::TextureFormat::BC4RUnorm:
        case wgpu::TextureFormat::BC5RGSnorm:
        case wgpu::TextureFormat::BC5RGUnorm:
        case wgpu::TextureFormat::BC6HRGBFloat:
        case wgpu::TextureFormat::BC6HRGBUfloat:
        case wgpu::TextureFormat::BC7RGBAUnorm:
        case wgpu::TextureFormat::BC7RGBAUnormSrgb:
#endif

        case wgpu::TextureFormat::ETC2RGB8Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatETC2_RGB8;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ETC2RGB8UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatETC2_RGB8_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ETC2RGB8A1Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatETC2_RGB8A1;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ETC2RGB8A1UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatETC2_RGB8A1_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ETC2RGBA8Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatEAC_RGBA8;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ETC2RGBA8UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatEAC_RGBA8_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::EACR11Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatEAC_R11Unorm;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::EACR11Snorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatEAC_R11Snorm;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::EACRG11Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatEAC_RG11Unorm;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::EACRG11Snorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatEAC_RG11Snorm;
            } else {
                UNREACHABLE();
            }

        case wgpu::TextureFormat::ASTC4x4Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_4x4_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC4x4UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_4x4_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC5x4Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_5x4_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC5x4UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_5x4_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC5x5Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_5x5_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC5x5UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_5x5_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC6x5Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_6x5_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC6x5UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_6x5_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC6x6Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_6x6_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC6x6UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_6x6_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC8x5Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_8x5_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC8x5UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_8x5_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC8x6Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_8x6_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC8x6UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_8x6_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC8x8Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_8x8_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC8x8UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_8x8_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x5Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x5_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x5UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x5_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x6Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x6_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x6UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x6_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x8Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x8_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x8UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x8_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x10Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x10_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC10x10UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_10x10_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC12x10Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_12x10_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC12x10UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_12x10_sRGB;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC12x12Unorm:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_12x12_LDR;
            } else {
                UNREACHABLE();
            }
        case wgpu::TextureFormat::ASTC12x12UnormSrgb:
            if (@available(macOS 11.0, iOS 8.0, *)) {
                return MTLPixelFormatASTC_12x12_sRGB;
            } else {
                UNREACHABLE();
            }

        case wgpu::TextureFormat::R8BG8Biplanar420Unorm:
        case wgpu::TextureFormat::Undefined:
            UNREACHABLE();
    }
}

MaybeError ValidateIOSurfaceCanBeWrapped(const DeviceBase*,
                                         const TextureDescriptor* descriptor,
                                         IOSurfaceRef ioSurface) {
    DAWN_INVALID_IF(descriptor->dimension != wgpu::TextureDimension::e2D,
                    "Texture dimension (%s) is not %s.", descriptor->dimension,
                    wgpu::TextureDimension::e2D);

    DAWN_INVALID_IF(descriptor->mipLevelCount != 1, "Mip level count (%u) is not 1.",
                    descriptor->mipLevelCount);

    DAWN_INVALID_IF(descriptor->size.depthOrArrayLayers != 1, "Array layer count (%u) is not 1.",
                    descriptor->size.depthOrArrayLayers);

    DAWN_INVALID_IF(descriptor->sampleCount != 1, "Sample count (%u) is not 1.",
                    descriptor->sampleCount);

    uint32_t surfaceWidth = IOSurfaceGetWidth(ioSurface);
    uint32_t surfaceHeight = IOSurfaceGetHeight(ioSurface);

    DAWN_INVALID_IF(
        descriptor->size.width != surfaceWidth || descriptor->size.height != surfaceHeight ||
            descriptor->size.depthOrArrayLayers != 1,
        "IOSurface size (width: %u, height %u, depth: 1) doesn't match descriptor size %s.",
        surfaceWidth, surfaceHeight, &descriptor->size);

    wgpu::TextureFormat ioSurfaceFormat;
    DAWN_TRY_ASSIGN(ioSurfaceFormat,
                    GetFormatEquivalentToIOSurfaceFormat(IOSurfaceGetPixelFormat(ioSurface)));
    DAWN_INVALID_IF(descriptor->format != ioSurfaceFormat,
                    "IOSurface format (%s) doesn't match the descriptor format (%s).",
                    ioSurfaceFormat, descriptor->format);

    return {};
}

NSRef<MTLTextureDescriptor> Texture::CreateMetalTextureDescriptor() const {
    NSRef<MTLTextureDescriptor> mtlDescRef = AcquireNSRef([MTLTextureDescriptor new]);
    MTLTextureDescriptor* mtlDesc = mtlDescRef.Get();

    mtlDesc.width = GetWidth();
    mtlDesc.sampleCount = GetSampleCount();
    // Metal only allows format reinterpretation to happen on swizzle pattern or conversion
    // between linear space and sRGB. For example, creating bgra8Unorm texture view on
    // rgba8Unorm texture or creating rgba8Unorm_srgb texture view on rgab8Unorm texture.
    mtlDesc.usage = MetalTextureUsage(GetFormat(), GetInternalUsage());
    mtlDesc.pixelFormat = MetalPixelFormat(GetDevice(), GetFormat().format);
    if (GetDevice()->IsToggleEnabled(Toggle::MetalUseCombinedDepthStencilFormatForStencil8) &&
        GetFormat().format == wgpu::TextureFormat::Stencil8) {
        // If we used a combined depth stencil format instead of stencil8, we need
        // MTLTextureUsagePixelFormatView to reinterpet as stencil8.
        mtlDesc.usage |= MTLTextureUsagePixelFormatView;
    }
    mtlDesc.mipmapLevelCount = GetNumMipLevels();

    // Create the texture in private storage mode unless the client has
    // specified that this texture is for a transient attachment, in which case
    // the texture should be created in memoryless storage mode.
    mtlDesc.storageMode = MTLStorageModePrivate;
    if (@available(macOS 11.0, iOS 10.0, *)) {
        if (GetInternalUsage() & wgpu::TextureUsage::TransientAttachment) {
            mtlDesc.storageMode = MTLStorageModeMemoryless;
        }
    }

    // Choose the correct MTLTextureType and paper over differences in how the array layer count
    // is specified.
    switch (GetDimension()) {
        case wgpu::TextureDimension::e1D:
            mtlDesc.arrayLength = 1;
            mtlDesc.depth = 1;
            ASSERT(mtlDesc.sampleCount == 1);
            mtlDesc.textureType = MTLTextureType1D;
            break;

        case wgpu::TextureDimension::e2D:
            mtlDesc.height = GetHeight();
            mtlDesc.arrayLength = GetArrayLayers();
            mtlDesc.depth = 1;
            if (mtlDesc.arrayLength > 1) {
                ASSERT(mtlDesc.sampleCount == 1);
                mtlDesc.textureType = MTLTextureType2DArray;
            } else if (mtlDesc.sampleCount > 1) {
                mtlDesc.textureType = MTLTextureType2DMultisample;
            } else {
                mtlDesc.textureType = MTLTextureType2D;
            }
            break;
        case wgpu::TextureDimension::e3D:
            mtlDesc.height = GetHeight();
            mtlDesc.depth = GetDepth();
            mtlDesc.arrayLength = 1;
            ASSERT(mtlDesc.sampleCount == 1);
            mtlDesc.textureType = MTLTextureType3D;
            break;
    }

    return mtlDescRef;
}

// static
ResultOrError<Ref<Texture>> Texture::Create(Device* device, const TextureDescriptor* descriptor) {
    Ref<Texture> texture = AcquireRef(new Texture(device, descriptor, TextureState::OwnedInternal));
    DAWN_TRY(texture->InitializeAsInternalTexture(descriptor));
    return texture;
}

// static
ResultOrError<Ref<Texture>> Texture::CreateFromIOSurface(
    Device* device,
    const ExternalImageDescriptor* descriptor,
    IOSurfaceRef ioSurface,
    std::vector<MTLSharedEventAndSignalValue> waitEvents) {
    const TextureDescriptor* textureDescriptor = FromAPI(descriptor->cTextureDescriptor);

    Ref<Texture> texture =
        AcquireRef(new Texture(device, textureDescriptor, TextureState::OwnedExternal));
    DAWN_TRY(texture->InitializeFromIOSurface(descriptor, textureDescriptor, ioSurface,
                                              std::move(waitEvents)));
    return texture;
}

// static
Ref<Texture> Texture::CreateWrapping(Device* device,
                                     const TextureDescriptor* descriptor,
                                     NSPRef<id<MTLTexture>> wrapped) {
    Ref<Texture> texture = AcquireRef(new Texture(device, descriptor, TextureState::OwnedInternal));
    texture->InitializeAsWrapping(descriptor, std::move(wrapped));
    return texture;
}

MaybeError Texture::InitializeAsInternalTexture(const TextureDescriptor* descriptor) {
    Device* device = ToBackend(GetDevice());

    NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor();
    mMtlUsage = [*mtlDesc usage];
    mMtlTexture = AcquireNSPRef([device->GetMTLDevice() newTextureWithDescriptor:mtlDesc.Get()]);

    if (mMtlTexture == nil) {
        return DAWN_OUT_OF_MEMORY_ERROR("Failed to allocate texture.");
    }
    SetLabelImpl();

    if (device->IsToggleEnabled(Toggle::NonzeroClearResourcesOnCreationForTesting)) {
        DAWN_TRY(ClearTexture(device->GetPendingCommandContext(), GetAllSubresources(),
                              TextureBase::ClearValue::NonZero));
    } else if (ShouldKeepInitialized()) {
        DAWN_TRY(ClearTexture(device->GetPendingCommandContext(), GetAllSubresources(),
                              TextureBase::ClearValue::Zero));
    }

    return {};
}

void Texture::InitializeAsWrapping(const TextureDescriptor* descriptor,
                                   NSPRef<id<MTLTexture>> wrapped) {
    NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor();
    mMtlUsage = [*mtlDesc usage];
    mMtlTexture = std::move(wrapped);
    SetLabelImpl();
}

MaybeError Texture::InitializeFromIOSurface(const ExternalImageDescriptor* descriptor,
                                            const TextureDescriptor* textureDescriptor,
                                            IOSurfaceRef ioSurface,
                                            std::vector<MTLSharedEventAndSignalValue> waitEvents) {
    DAWN_INVALID_IF(
        GetInternalUsage() & wgpu::TextureUsage::TransientAttachment,
        "Usage flags (%s) include %s, which is not compatible with creation from IOSurface.",
        GetInternalUsage(), wgpu::TextureUsage::TransientAttachment);

    mIOSurface = ioSurface;
    mWaitEvents = std::move(waitEvents);

    // Uses WGPUTexture which wraps multiplanar ioSurface needs to create
    // texture view explicitly. Wrap the ioSurface and delay to extract
    // MTLTexture from the plane of it when creating texture view.
    // WGPUTexture which wraps non-multplanar ioSurface needs to support
    // ops that doesn't require creating texture view(e.g. copy). Extract
    // MTLTexture from such ioSurface to support this.
    if (!GetFormat().IsMultiPlanar()) {
        Device* device = ToBackend(GetDevice());

        NSRef<MTLTextureDescriptor> mtlDesc = CreateMetalTextureDescriptor();
        [*mtlDesc setStorageMode:kIOSurfaceStorageMode];

        mMtlUsage = [*mtlDesc usage];
        mMtlTexture = AcquireNSPRef([device->GetMTLDevice() newTextureWithDescriptor:mtlDesc.Get()
                                                                           iosurface:ioSurface
                                                                               plane:0]);
        SetLabelImpl();
    }
    SetIsSubresourceContentInitialized(descriptor->isInitialized, GetAllSubresources());
    return {};
}

void Texture::SynchronizeTextureBeforeUse(CommandRecordingContext* commandContext) {
    if (@available(macOS 10.14, *)) {
        if (!mWaitEvents.empty()) {
            // There may be an open blit encoder from a copy command or writeBuffer.
            // Wait events are only allowed if there is no encoder open.
            commandContext->EndBlit();
        }
        auto commandBuffer = commandContext->GetCommands();
        // Consume the wait events on the texture. They will be empty after this loop.
        for (auto waitEvent : std::move(mWaitEvents)) {
            id rawEvent = *waitEvent.sharedEvent;
            id<MTLSharedEvent> sharedEvent = static_cast<id<MTLSharedEvent>>(rawEvent);
            [commandBuffer encodeWaitForEvent:sharedEvent value:waitEvent.signaledValue];
        }
    }
}

void Texture::IOSurfaceEndAccess(ExternalImageIOSurfaceEndAccessDescriptor* descriptor) {
    ASSERT(descriptor);
    ToBackend(GetDevice())->ExportLastSignaledEvent(descriptor);
    descriptor->isInitialized = IsSubresourceContentInitialized(GetAllSubresources());
    // Destroy the texture as it should not longer be used after EndAccess.
    Destroy();
}

Texture::Texture(DeviceBase* dev, const TextureDescriptor* desc, TextureState st)
    : TextureBase(dev, desc, st) {}

Texture::~Texture() {}

void Texture::DestroyImpl() {
    TextureBase::DestroyImpl();
    mMtlTexture = nullptr;
    mIOSurface = nullptr;
}

void Texture::SetLabelImpl() {
    SetDebugName(GetDevice(), mMtlTexture.Get(), "Dawn_Texture", GetLabel());
}

id<MTLTexture> Texture::GetMTLTexture() const {
    return mMtlTexture.Get();
}

IOSurfaceRef Texture::GetIOSurface() {
    return mIOSurface.Get();
}

NSPRef<id<MTLTexture>> Texture::CreateFormatView(wgpu::TextureFormat format) {
    if (GetFormat().format == format) {
        return mMtlTexture;
    }

    ASSERT(AllowFormatReinterpretationWithoutFlag(MetalPixelFormat(GetDevice(), GetFormat().format),
                                                  MetalPixelFormat(GetDevice(), format)));
    return AcquireNSPRef(
        [mMtlTexture.Get() newTextureViewWithPixelFormat:MetalPixelFormat(GetDevice(), format)]);
}

bool Texture::ShouldKeepInitialized() const {
    return GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse) &&
           GetDevice()->IsToggleEnabled(
               Toggle::MetalKeepMultisubresourceDepthStencilTexturesInitialized) &&
           GetFormat().HasDepthOrStencil() && (GetArrayLayers() > 1 || GetNumMipLevels() > 1);
}

MaybeError Texture::ClearTexture(CommandRecordingContext* commandContext,
                                 const SubresourceRange& range,
                                 TextureBase::ClearValue clearValue) {
    Device* device = ToBackend(GetDevice());

    const uint8_t clearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0 : 1;
    const double dClearColor = (clearValue == TextureBase::ClearValue::Zero) ? 0.0 : 1.0;

    if ((mMtlUsage & MTLTextureUsageRenderTarget) != 0) {
        ASSERT(GetFormat().isRenderable);

        // End the blit encoder if it is open.
        commandContext->EndBlit();

        if (GetFormat().HasDepthOrStencil()) {
            // Create a render pass to clear each subresource.
            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
                 ++level) {
                for (uint32_t arrayLayer = range.baseArrayLayer;
                     arrayLayer < range.baseArrayLayer + range.layerCount; arrayLayer++) {
                    if (clearValue == TextureBase::ClearValue::Zero &&
                        IsSubresourceContentInitialized(SubresourceRange::SingleMipAndLayer(
                            level, arrayLayer, range.aspects))) {
                        // Skip lazy clears if already initialized.
                        continue;
                    }

                    // Note that this creates a descriptor that's autoreleased so we don't use
                    // AcquireNSRef
                    NSRef<MTLRenderPassDescriptor> descriptorRef =
                        [MTLRenderPassDescriptor renderPassDescriptor];
                    MTLRenderPassDescriptor* descriptor = descriptorRef.Get();

                    // At least one aspect needs clearing. Iterate the aspects individually to
                    // determine which to clear.
                    for (Aspect aspect : IterateEnumMask(range.aspects)) {
                        if (clearValue == TextureBase::ClearValue::Zero &&
                            IsSubresourceContentInitialized(
                                SubresourceRange::SingleMipAndLayer(level, arrayLayer, aspect))) {
                            // Skip lazy clears if already initialized.
                            continue;
                        }

                        ASSERT(GetDimension() == wgpu::TextureDimension::e2D);
                        switch (aspect) {
                            case Aspect::Depth:
                                descriptor.depthAttachment.texture = GetMTLTexture();
                                descriptor.depthAttachment.level = level;
                                descriptor.depthAttachment.slice = arrayLayer;
                                descriptor.depthAttachment.loadAction = MTLLoadActionClear;
                                descriptor.depthAttachment.storeAction = MTLStoreActionStore;
                                descriptor.depthAttachment.clearDepth = dClearColor;
                                break;
                            case Aspect::Stencil:
                                descriptor.stencilAttachment.texture = GetMTLTexture();
                                descriptor.stencilAttachment.level = level;
                                descriptor.stencilAttachment.slice = arrayLayer;
                                descriptor.stencilAttachment.loadAction = MTLLoadActionClear;
                                descriptor.stencilAttachment.storeAction = MTLStoreActionStore;
                                descriptor.stencilAttachment.clearStencil =
                                    static_cast<uint32_t>(clearColor);
                                break;
                            default:
                                UNREACHABLE();
                        }
                    }

                    DAWN_TRY(
                        EncodeEmptyMetalRenderPass(device, commandContext, descriptor,
                                                   GetMipLevelSingleSubresourceVirtualSize(level)));
                }
            }
        } else {
            ASSERT(GetFormat().IsColor());
            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
                 ++level) {
                // Create multiple render passes with each subresource as a color attachment to
                // clear them all. Only do this for array layers to ensure all attachments have
                // the same size.
                NSRef<MTLRenderPassDescriptor> descriptor;
                uint32_t attachment = 0;

                uint32_t depth = GetMipLevelSingleSubresourceVirtualSize(level).depthOrArrayLayers;

                for (uint32_t arrayLayer = range.baseArrayLayer;
                     arrayLayer < range.baseArrayLayer + range.layerCount; arrayLayer++) {
                    if (clearValue == TextureBase::ClearValue::Zero &&
                        IsSubresourceContentInitialized(SubresourceRange::SingleMipAndLayer(
                            level, arrayLayer, Aspect::Color))) {
                        // Skip lazy clears if already initialized.
                        continue;
                    }

                    for (uint32_t z = 0; z < depth; ++z) {
                        if (descriptor == nullptr) {
                            // Note that this creates a descriptor that's autoreleased so we
                            // don't use AcquireNSRef
                            descriptor = [MTLRenderPassDescriptor renderPassDescriptor];
                        }

                        [*descriptor colorAttachments][attachment].texture = GetMTLTexture();
                        [*descriptor colorAttachments][attachment].loadAction = MTLLoadActionClear;
                        [*descriptor colorAttachments][attachment].storeAction =
                            MTLStoreActionStore;
                        [*descriptor colorAttachments][attachment].clearColor =
                            MTLClearColorMake(dClearColor, dClearColor, dClearColor, dClearColor);
                        [*descriptor colorAttachments][attachment].level = level;
                        [*descriptor colorAttachments][attachment].slice = arrayLayer;
                        [*descriptor colorAttachments][attachment].depthPlane = z;

                        attachment++;

                        if (attachment == kMaxColorAttachments) {
                            attachment = 0;
                            DAWN_TRY(EncodeEmptyMetalRenderPass(
                                device, commandContext, descriptor.Get(),
                                GetMipLevelSingleSubresourceVirtualSize(level)));
                            descriptor = nullptr;
                        }
                    }
                }

                if (descriptor != nullptr) {
                    DAWN_TRY(
                        EncodeEmptyMetalRenderPass(device, commandContext, descriptor.Get(),
                                                   GetMipLevelSingleSubresourceVirtualSize(level)));
                }
            }
        }
    } else {
        ASSERT(!IsMultisampledTexture());

        // Encode a buffer to texture copy to clear each subresource.
        for (Aspect aspect : IterateEnumMask(range.aspects)) {
            // Compute the buffer size big enough to fill the largest mip.
            const TexelBlockInfo& blockInfo = GetFormat().GetAspectInfo(aspect).block;

            // Computations for the bytes per row / image height are done using the physical size
            // so that enough data is reserved for compressed textures.
            Extent3D largestMipSize = GetMipLevelSingleSubresourcePhysicalSize(range.baseMipLevel);
            uint32_t largestMipBytesPerRow =
                (largestMipSize.width / blockInfo.width) * blockInfo.byteSize;
            uint64_t largestMipBytesPerImage = static_cast<uint64_t>(largestMipBytesPerRow) *
                                               (largestMipSize.height / blockInfo.height);
            uint64_t bufferSize = largestMipBytesPerImage * largestMipSize.depthOrArrayLayers;

            if (bufferSize > std::numeric_limits<NSUInteger>::max()) {
                return DAWN_OUT_OF_MEMORY_ERROR("Unable to allocate buffer.");
            }

            DynamicUploader* uploader = device->GetDynamicUploader();
            UploadHandle uploadHandle;
            DAWN_TRY_ASSIGN(uploadHandle,
                            uploader->Allocate(bufferSize, device->GetPendingCommandSerial(),
                                               blockInfo.byteSize));
            memset(uploadHandle.mappedBuffer, clearColor, bufferSize);

            id<MTLBuffer> uploadBuffer = ToBackend(uploadHandle.stagingBuffer)->GetMTLBuffer();

            for (uint32_t level = range.baseMipLevel; level < range.baseMipLevel + range.levelCount;
                 ++level) {
                Extent3D virtualSize = GetMipLevelSingleSubresourceVirtualSize(level);

                for (uint32_t arrayLayer = range.baseArrayLayer;
                     arrayLayer < range.baseArrayLayer + range.layerCount; ++arrayLayer) {
                    if (clearValue == TextureBase::ClearValue::Zero &&
                        IsSubresourceContentInitialized(
                            SubresourceRange::SingleMipAndLayer(level, arrayLayer, aspect))) {
                        // Skip lazy clears if already initialized.
                        continue;
                    }

                    MTLBlitOption blitOption = ComputeMTLBlitOption(aspect);
                    [commandContext->EnsureBlit()
                             copyFromBuffer:uploadBuffer
                               sourceOffset:uploadHandle.startOffset
                          sourceBytesPerRow:largestMipBytesPerRow
                        sourceBytesPerImage:largestMipBytesPerImage
                                 sourceSize:MTLSizeMake(virtualSize.width, virtualSize.height,
                                                        virtualSize.depthOrArrayLayers)
                                  toTexture:GetMTLTexture()
                           destinationSlice:arrayLayer
                           destinationLevel:level
                          destinationOrigin:MTLOriginMake(0, 0, 0)
                                    options:blitOption];
                }
            }
        }
    }
    return {};
}

MTLBlitOption Texture::ComputeMTLBlitOption(Aspect aspect) const {
    ASSERT(HasOneBit(aspect));
    ASSERT(GetFormat().aspects & aspect);
    MTLPixelFormat format = MetalPixelFormat(GetDevice(), GetFormat().format);

    if (format == MTLPixelFormatDepth32Float_Stencil8) {
        // We only provide a blit option if the format has both depth and stencil.
        // It is invalid to provide a blit option otherwise.
        switch (aspect) {
            case Aspect::Depth:
                return MTLBlitOptionDepthFromDepthStencil;
            case Aspect::Stencil:
                return MTLBlitOptionStencilFromDepthStencil;
            default:
                UNREACHABLE();
        }
    }
    return MTLBlitOptionNone;
}

MaybeError Texture::EnsureSubresourceContentInitialized(CommandRecordingContext* commandContext,
                                                        const SubresourceRange& range) {
    if (!GetDevice()->IsToggleEnabled(Toggle::LazyClearResourceOnFirstUse)) {
        return {};
    }
    if (!IsSubresourceContentInitialized(range)) {
        // If subresource has not been initialized, clear it to black as it could
        // contain dirty bits from recycled memory
        DAWN_TRY(ClearTexture(commandContext, range, TextureBase::ClearValue::Zero));
        SetIsSubresourceContentInitialized(true, range);
        GetDevice()->IncrementLazyClearCountForTesting();
    }
    return {};
}

// static
ResultOrError<Ref<TextureView>> TextureView::Create(TextureBase* texture,
                                                    const TextureViewDescriptor* descriptor) {
    Ref<TextureView> view = AcquireRef(new TextureView(texture, descriptor));
    DAWN_TRY(view->Initialize(descriptor));
    return view;
}

MaybeError TextureView::Initialize(const TextureViewDescriptor* descriptor) {
    DeviceBase* device = GetDevice();
    Texture* texture = ToBackend(GetTexture());

    // Texture could be destroyed by the time we make a view.
    if (GetTexture()->GetTextureState() == Texture::TextureState::Destroyed) {
        return {};
    }

    id<MTLTexture> mtlTexture = texture->GetMTLTexture();

    bool needsNewView = RequiresCreatingNewTextureView(texture, descriptor);
    if (device->IsToggleEnabled(Toggle::MetalUseCombinedDepthStencilFormatForStencil8) &&
        GetTexture()->GetFormat().format == wgpu::TextureFormat::Stencil8) {
        // If MetalUseCombinedDepthStencilFormatForStencil8 is true and the format is Stencil8,
        // we used a combined format instead on texture allocation.
        // We need a new view to view it as stencil8.
        needsNewView = true;
    }
    if (!needsNewView) {
        mMtlTextureView = mtlTexture;
    } else if (texture->GetFormat().IsMultiPlanar()) {
        NSRef<MTLTextureDescriptor> mtlDescRef = AcquireNSRef([MTLTextureDescriptor new]);
        MTLTextureDescriptor* mtlDesc = mtlDescRef.Get();

        mtlDesc.sampleCount = texture->GetSampleCount();
        mtlDesc.usage = MetalTextureUsage(texture->GetFormat(), texture->GetInternalUsage());
        mtlDesc.pixelFormat = MetalPixelFormat(device, descriptor->format);
        mtlDesc.mipmapLevelCount = texture->GetNumMipLevels();
        mtlDesc.storageMode = kIOSurfaceStorageMode;

        uint32_t plane = GetIOSurfacePlane(descriptor->aspect);
        mtlDesc.width = IOSurfaceGetWidthOfPlane(texture->GetIOSurface(), plane);
        mtlDesc.height = IOSurfaceGetHeightOfPlane(texture->GetIOSurface(), plane);

        // Multiplanar texture is validated to only have single layer, single mipLevel
        // and 2d textures (depth == 1)
        ASSERT(texture->GetArrayLayers() == 1 &&
               texture->GetDimension() == wgpu::TextureDimension::e2D &&
               texture->GetNumMipLevels() == 1);
        mtlDesc.arrayLength = 1;
        mtlDesc.depth = 1;

        mMtlTextureView = AcquireNSPRef([ToBackend(GetDevice())->GetMTLDevice()
            newTextureWithDescriptor:mtlDesc
                           iosurface:texture->GetIOSurface()
                               plane:plane]);
        if (mMtlTextureView == nil) {
            return DAWN_INTERNAL_ERROR("Failed to create MTLTexture view for external texture.");
        }
    } else {
        MTLPixelFormat viewFormat = MetalPixelFormat(device, descriptor->format);
        MTLPixelFormat textureFormat = MetalPixelFormat(device, GetTexture()->GetFormat().format);

        Aspect aspect = SelectFormatAspects(GetFormat(), descriptor->aspect);
        if (aspect == Aspect::Stencil && textureFormat != MTLPixelFormatStencil8) {
            ASSERT(textureFormat == MTLPixelFormatDepth32Float_Stencil8);
            viewFormat = MTLPixelFormatX32_Stencil8;
        } else if (GetTexture()->GetFormat().HasDepth() && GetTexture()->GetFormat().HasStencil()) {
            // Depth-only views for depth/stencil textures in Metal simply use the original
            // texture's format.
            viewFormat = textureFormat;
        }

        MTLTextureType textureViewType =
            MetalTextureViewType(descriptor->dimension, texture->GetSampleCount());
        auto mipLevelRange = NSMakeRange(descriptor->baseMipLevel, descriptor->mipLevelCount);
        auto arrayLayerRange = NSMakeRange(descriptor->baseArrayLayer, descriptor->arrayLayerCount);

        mMtlTextureView = AcquireNSPRef([mtlTexture newTextureViewWithPixelFormat:viewFormat
                                                                      textureType:textureViewType
                                                                           levels:mipLevelRange
                                                                           slices:arrayLayerRange]);
        if (mMtlTextureView == nil) {
            return DAWN_INTERNAL_ERROR("Failed to create MTLTexture view.");
        }
    }

    SetLabelImpl();
    return {};
}

void TextureView::DestroyImpl() {
    mMtlTextureView = nil;
}

void TextureView::SetLabelImpl() {
    SetDebugName(GetDevice(), mMtlTextureView.Get(), "Dawn_TextureView", GetLabel());
}

id<MTLTexture> TextureView::GetMTLTexture() const {
    ASSERT(mMtlTextureView != nullptr);
    return mMtlTextureView.Get();
}

TextureView::AttachmentInfo TextureView::GetAttachmentInfo() const {
    ASSERT(GetTexture()->GetInternalUsage() & wgpu::TextureUsage::RenderAttachment);
    // Use our own view if the formats do not match.
    // If the formats do not match, format reinterpretation will be required.
    // Note: Depth/stencil formats don't support reinterpretation.
    // Also, we compute |useOwnView| here instead of relying on whether or not
    // a view was created in Initialize, because rendering to a depth/stencil
    // texture on Metal only works when using the original texture, not a view.
    bool useOwnView = GetFormat().format != GetTexture()->GetFormat().format &&
                      !GetTexture()->GetFormat().HasDepthOrStencil();
    if (useOwnView) {
        ASSERT(mMtlTextureView.Get());
        return {mMtlTextureView, 0, 0};
    }
    AttachmentInfo info;
    info.texture = ToBackend(GetTexture())->GetMTLTexture();
    info.baseMipLevel = GetBaseMipLevel();
    info.baseArrayLayer = GetBaseArrayLayer();
    return info;
}

}  // namespace dawn::native::metal
